﻿using System.Collections;
using NewLife;
using NewLife.Collections;
using NewLife.Log;
using NewLife.Model;
using NewLife.Reflection;
using XCode.Configuration;

namespace XCode.Membership;

/// <summary>日志提供者。提供业务日志输出到数据库的功能</summary>
public class LogProvider
{
    #region 基本功能
    /// <summary>写日志</summary>
    /// <param name="category">类型</param>
    /// <param name="action">操作</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    [Obsolete]
    public virtual void WriteLog(String category, String action, String remark, Int32 userid = 0, String? name = null, String? ip = null) => WriteLog(category, action, true, remark, userid, name, ip);

    /// <summary>写日志</summary>
    /// <param name="type">类型</param>
    /// <param name="action">操作</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    [Obsolete]
    public virtual void WriteLog(Type type, String action, String remark, Int32 userid = 0, String? name = null, String? ip = null) => WriteLog(type, action, true, remark, userid, name, ip);

    /// <summary>创建日志，未写入</summary>
    /// <param name="category">类型</param>
    /// <param name="action">操作</param>
    /// <param name="success">成功</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    public virtual Log CreateLog(String category, String action, Boolean success, String remark, Int32 userid = 0, String? name = null, String? ip = null)
    {
        if (category.IsNullOrEmpty()) throw new ArgumentNullException(nameof(category));

        var factory = EntityFactory.CreateFactory(typeof(Log));
        var log = (factory.Create() as Log)!;
        log.Category = category;
        log.Action = action;
        log.Success = success;

        // 加上关联编号
        if (remark.StartsWithIgnoreCase("ID="))
        {
            if (factory.Table.FindByName("Id") is FieldItem fi && fi.Type.IsInt())
                log.LinkID = remark.Substring("ID=", ",").ToLong();
        }

        if (userid > 0) log.CreateUserID = userid;
        if (!name.IsNullOrEmpty()) log.UserName = name;
        if (!ip.IsNullOrEmpty()) log.CreateIP = ip;

        // 获取当前登录信息
        if (log.CreateUserID == 0 || name.IsNullOrEmpty())
        {
            // 当前登录用户
            var prv = Provider2 ?? ManageProvider.Provider;
            //var user = prv?.Current ?? HttpContext.Current?.User?.Identity as IManageUser;
            var user = prv?.Current;
            if (user != null)
            {
                if (log.CreateUserID == 0) log.CreateUserID = user.ID;
                if (log.UserName.IsNullOrEmpty()) log.UserName = user + "";
            }
        }
        if (log.CreateIP.IsNullOrEmpty()) log.CreateIP = ManageProvider.UserHost;

        //log.TraceId = DefaultSpan.Current?.TraceId;
        log.Remark = remark;
        log.CreateTime = DateTime.Now;

        return log;
    }

    /// <summary>创建日志，未写入</summary>
    /// <param name="type">实体类型</param>
    /// <param name="action">操作</param>
    /// <param name="success">成功</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    /// <param name="linkid">关联编号</param>
    public virtual Log CreateLog(Type type, String action, Boolean success, String remark, Int32 userid = 0, String? name = null, String? ip = null, Int64 linkid = 0)
    {
        if (type == null) throw new ArgumentNullException(nameof(type));

        var cat = "";
        if (type.As<IEntity>())
        {
            var fact = EntityFactory.CreateFactory(type);
            if (fact != null) cat = fact.Table.DataTable.DisplayName;
        }
        if (cat.IsNullOrEmpty()) cat = type.GetDisplayName() ?? type.GetDescription() ?? type.Name;

        var log = CreateLog(cat, action, success, remark, userid, name, ip);
        if (linkid > 0) log.LinkID = linkid;

        return log;
    }

    /// <summary>写日志</summary>
    /// <param name="category">类型</param>
    /// <param name="action">操作</param>
    /// <param name="success">成功</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    public virtual void WriteLog(String category, String action, Boolean success, String remark, Int32 userid = 0, String? name = null, String? ip = null)
    {
        if (!Enable) return;

        var log = CreateLog(category, action, success, remark, userid, name, ip);

        log.SaveAsync();
    }

    /// <summary>写日志</summary>
    /// <param name="type">类型</param>
    /// <param name="action">操作</param>
    /// <param name="success">成功</param>
    /// <param name="remark">备注</param>
    /// <param name="userid">用户</param>
    /// <param name="name">名称</param>
    /// <param name="ip">地址</param>
    public virtual void WriteLog(Type type, String action, Boolean success, String remark, Int32 userid = 0, String? name = null, String? ip = null)
    {
        if (!Enable) return;

        var log = CreateLog(type, action, success, remark, userid, name, ip);

        log.SaveAsync();
    }

    /// <summary>输出实体对象日志</summary>
    /// <param name="action">操作</param>
    /// <param name="entity">实体</param>
    /// <param name="error">错误信息</param>
    public void WriteLog(String action, IEntity entity, String? error = null)
    {
        if (!Enable) return;

        var type = entity.GetType();
        var fact = EntityFactory.CreateFactory(type);

        // 构造字段数据的字符串表示形式
        var sb = Pool.StringBuilder.Get();
        if (error.IsNullOrEmpty()) sb.Append(error);
        // 修改操作生成修改前后的数据对比
        if (action == "修改" || action == "Update")
        {
            var ds = entity.Dirtys.GetDictionary();
            foreach (var fi in fact.Fields)
            {
                if (fi.PrimaryKey)
                {
                    sb.Separate(",").AppendFormat("{0}={1}", fi.Name, entity[fi.Name]);
                }
                else
                {
                    if (!ds.TryGetValue(fi.Name, out var v1)) continue;

                    var v2 = entity[fi.Name];

                    // 日志里面不要出现密码
                    if (fi.Name.EqualIgnoreCase("pass", "password")) v1 = v2 = null;

                    if (v1 is DateTime dt1)
                        v1 = dt1.ToFullString();
                    else if (v1 is IList list)
                        v1 = list.Join(",");

                    if (v2 is DateTime dt2)
                        v2 = dt2.ToFullString();
                    else if (v2 is IList list)
                        v2 = list.Join(",");

                    sb.Separate(",").AppendFormat("{0}={1} -> {2}", fi.Name, v1, v2);
                }
            }
        }
        else
        {
            foreach (var fi in fact.Fields)
            {
                var v = entity[fi.Name];
                // 空字符串不写日志
                if (action is "添加" or "删除" or "Insert" or "Delete")
                {
                    if (v + "" == "") continue;
                    if (v is Boolean b && !b) continue;
                    if (v is Int32 vi && vi == 0) continue;
                    if (v is DateTime dt && dt == DateTime.MinValue) continue;
                }

                // 日志里面不要出现密码
                if (fi.Name.EqualIgnoreCase("pass", "password")) v = null;

                if (v is DateTime dt2)
                    v = dt2.ToFullString();
                else if (v is IList list)
                    v = list.Join(",");

                sb.Separate(",").AppendFormat("{0}={1}", fi.Name, v);
            }
        }

        // 对象链接
        var linkId = 0L;
        var uk = fact.Unique;
        if (uk != null && uk.Type.IsInt()) linkId = entity[uk.Name].ToLong();

        var userid = 0;
        var name = "";
        if (entity is IManageUser user)
        {
            userid = user.ID;
            name = user + "";
        }

        //WriteLog(entity.GetType(), action, error.IsNullOrEmpty(), sb.Put(true), userid, name);
        var category = fact.Table.DataTable.DisplayName;
        if (category.IsNullOrEmpty()) category = type.GetDisplayName() ?? type.GetDescription() ?? type.Name;

        var log = CreateLog(category, action, error.IsNullOrEmpty(), sb.Put(true), userid, name);
        log.LinkID = linkId;

        log.SaveAsync();
    }

    /// <summary>是否使用日志</summary>
    public Boolean Enable { get; set; } = true;
    #endregion

    #region 日志转换
    /// <summary>转为标准日志接口</summary>
    /// <param name="category">日志分类</param>
    /// <returns></returns>
    public NewLife.Log.ILog AsLog(String category) => new DbLog { Provider = this, Category = category };

    class DbLog : Logger
    {
        public LogProvider? Provider { get; set; }

        public String? Category { get; set; }

        protected override void OnWrite(LogLevel level, String format, params Object?[] args)
        {
            var msg = String.Format(format, args);
            var act = "";
            var p = msg.IndexOf(' ');
            if (p > 0)
            {
                act = msg[..p].Trim();
                msg = msg[(p + 1)..].Trim();
            }

            // 从参数里提取用户对象
            var user = args.FirstOrDefault(e => e is IManageUser) as IManageUser;

            Provider?.WriteLog(Category!, act, true, msg, user?.ID ?? 0, user + "");
        }
    }
    #endregion

    #region 静态属性
    /// <summary>当前成员提供者</summary>
    public static LogProvider Provider { get; set; } = new LogProvider();

    /// <summary>当前用户提供者</summary>
    public IManageProvider Provider2 { get; set; }
    #endregion
}